/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_httpd.htm
 *
 * Zhiqim Httpd is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.httpd.nio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

import org.zhiqim.httpd.HttpListener;
import org.zhiqim.httpd.HttpServer;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Closes;
import org.zhiqim.kernel.util.Validates;

/**
 * HTTP非阻塞式监听器 
 * 
 * @version v1.0.0 @author zouzhigang 2014-3-21 新建与整理
 */
public class HttpNioListener extends HttpListener
{
    protected ServerSocketChannel serverSocket;
    protected Selector selector;
    
    public HttpNioListener(HttpServer server)
    {
        super(server);
    }
    
    /** 开启前置，HTTPS可重写 */
    protected void openBeforeInner()
    {
    }
    
    /** 新建封装的连接对象，HTTPS可重写 */
    protected HttpNioConnection newConnection(SocketChannel socket) throws IOException
    {
        InetSocketAddress addr = (InetSocketAddress)socket.getRemoteAddress();
        
        return new HttpNioConnection(this, socket, addr.getAddress().getHostAddress(), addr.getPort());
    }
    
    /************************************************************************************/
    //线程开启&关闭&运行
    /************************************************************************************/
    
    @Override /** 线程开启前 */
    protected boolean openBefore()
    {
        //1.开启前置
        openBeforeInner();
        
        //2.监听参数和线程池
        super.openBefore();
        
        try
        {//3.监听端口
            selector = Selector.open();
            
            serverSocket = ServerSocketChannel.open();
            serverSocket.bind(Validates.isEmpty(listenerIp)?new InetSocketAddress(listenerPort):new InetSocketAddress(InetAddress.getByName(listenerIp), listenerPort));
            serverSocket.configureBlocking(false);
            serverSocket.setOption(StandardSocketOptions.SO_REUSEADDR, true);
            serverSocket.register(selector, SelectionKey.OP_ACCEPT);
            return true;
        }
        catch(Exception e)
        {
            log.error("初始化ServerSocket时异常", e);
            throw Asserts.exception(e);
        }
    }

    @Override /** 线程关闭后 */
    protected void closeAfter()
    {
        if (serverSocket != null)
        {//关闭监听
            Closes.closeIgnoreException(serverSocket);
            serverSocket = null;
        }
        
        if (selector != null)
        {//关闭选择器
            Closes.closeIgnoreException(selector);
            selector = null;
        }
        
        //关闭父类的控制器/定时任务/连接列表
        super.closeAfter();
    }
    
    @Override /** 线程持续运行 */
    protected void loop()
    {
        try
        {
            int keyNum = selector.select(soTimeoutMs);
            if (keyNum == 0)
                return;
            
            Set<SelectionKey> keys = selector.selectedKeys();
            for (Iterator<SelectionKey> it = keys.iterator();it.hasNext();) 
            {
                SelectionKey key = it.next();
                it.remove();
                
                try
                {
                    if (!key.isValid() || key.interestOps() == 0)
                        continue;
                    
                    if (key.isAcceptable())
                    {//客户请求连接
                        onAccept(key);
                    }
                    else if (key.isReadable())
                    {//读数据
                        onRead(key);
                    }
                }
                catch(CancelledKeyException e)
                {//虽然在前面有isValid排除，但在linux中readOps还有可能触发CancelledKeyException
                }
                catch(Throwable e)
                {
                    key.cancel();
                }
            }
        }
        catch (IOException e)
        {
            close();
        }
    }
    
    /************************************************************************************/
    //NIO 接受连接&读客户端数据
    /************************************************************************************/
    
    /** 接受客户端连接 */
    protected void onAccept(SelectionKey key)
    {
        ServerSocketChannel server = (ServerSocketChannel)key.channel();
        
        HttpNioConnection conn = null;
        SocketChannel socket = null;
        
        try
        {
            socket = server.accept();
            socket.configureBlocking(false);
            socket.setOption(StandardSocketOptions.TCP_NODELAY, true);
            if (soLingerS >= 0)
            {//默认关闭连接时最长保留20秒时间用于发送未发送完的数据
                socket.setOption(StandardSocketOptions.SO_LINGER, soLingerS);
            }
            socket.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
            socket.setOption(StandardSocketOptions.SO_RCVBUF, soRecvBuf);
            socket.setOption(StandardSocketOptions.SO_SNDBUF, soSendBuf);
            
            conn = newConnection(socket);
            socket.register(selector, SelectionKey.OP_READ, add(conn));
        }
        catch (Throwable e)
        {
            if (conn != null)
            {//连接创建成功异常，关连接
                conn.close();
            }
            else if (socket != null)
            {//Socket异常，关Socket
                Closes.closeIgnoreException(socket);
            }
        }
    }
    
    /** 读客户端数据 */
    private void onRead(SelectionKey key)
    {
        HttpNioConnection conn = (HttpNioConnection)key.attachment();
        if (conn.isClosed())
        {//连接已关闭或正在关闭
            key.cancel();
            return;
        }
        
        if (!conn.tryLock())
        {//尝试获取活动锁，锁定状态的返回，未锁的置为锁定
            return;
        }
        
        //连接空闲且有数据，转到线程池控制器处理
        controller.execute(conn);
    }
}
